;*****************************************************************************************************
;+
; NAME:
;       IGL_TEXT
;
; PURPOSE:
;
;       This is a basic vizualisation object which represents a
;       text annotation. It is subclassed from TEXTLINE (Catalyst
;       Library) since it was an already nice implementation of
;       a text object.
;
;
; AUTHORS:
;
;        RAPHAEL SADOUN
;        Graduate Student
;        Institut d'Astrophysique de Paris
;        98 bis boulevard Arago
;        75014 PARIS FRANCE
;        E-mail: raphael.sadoun@gmail.com
;
; SUPERCLASSES:
;
;       TEXTLINE (Catalyst Library)
;
; SYNTAX:
;
;       text = Obj_New('IGL_Text')
;
;
; CLASS_STRUCTURE:
;
;       class = {IGL_Text, $      
;           INHERITS TEXTLINE $   ; This is a TEXTLINE object
;           clip:0B $             ; Flag indicating if the text is clipped
;          }
;
;
;
; MODIFICATION_HISTORY:
;
;       Written by: Raphael Sadoun, 24 April 2012
;-
;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::CONTROLPANEL
;
; PURPOSE:
;
;       This method creates a control panel for the graphic.
;
; SYNTAX:
;
;       self -> ControlPanel, baseObject
;
; ARGUMENTS:
;
;       baseObject:    The object reference of a base widget for this control to
;                      be added to. If not supplied, the control panel will be in a
;                      self contained window.
;
; KEYWORDS:
;
;       _EXTRA:       Any keywords appropriate for the CATCONTROLPANEL::INIT method.
;-
;*****************************************************************************************************
PRO IGL_Text::ControlPanel, baseObject, _EXTRA=extraKeywords

   @cat_pro_error_handler

   ;; Create a new control panel
   cp = OBJ_NEW ('CatControlPanel', self, PARENT=baseObject, COLUMN=1, $
      TITLE='IGL_Text Control Panel', _EXTRA=extraKeywords, /No_Cancel, /No_Apply, /No_OK)

   IF (NOT OBJ_VALID (cp)) THEN RETURN

   aproperties = Obj_New('PROPERTYSHEETWIDGET', cp, Value=self, Name='IGL_TEXT PROPERTYSHEET', YSize=13)
   aproperties -> SetProperty, Event_Object=self

   ;; Display the control panel if it created its own TLB.
   IF cp -> Created_Own_TLB(tlb) THEN tlb -> Draw, /Center

   self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::DRAW
;
; PURPOSE:
;
;       This method draws the graphic in the current display window.
;
; SYNTAX:
;
;       self -> Draw
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       _EXTRA:    Any keyword accepted by the CATATOM::Draw method.
;
;-
;*****************************************************************************************************
PRO IGL_Text::Draw, _EXTRA=extra

   @cat_pro_error_handler

   ;; The following code is a slight modification of the
   ;; TEXTLINE::Draw method. It adds support for teX-like syntax by
   ;; calling the TexToIDL routine for processing the text string.

   ;; If there is no text, then return immediately.
   IF Ptr_Valid(self._dataPtr) EQ 0 THEN RETURN
   IF *self._dataPtr EQ "" THEN RETURN

   ;; If the text is invisible, then return immediately.
   IF self.visible EQ 0 THEN RETURN

   ;; Save current color table
   TVLCT, r, g, b, /GET

   ;; Switch to decomposed mode
   SetDecomposedState, 1, currentState=currentState

   ;; Make sure you have a string and not something else.
   IF Size(*self._dataPtr, /TName) NE 'STRING' THEN $
      text = StrTrim(*self._dataPtr, 2) ELSE $
      text = *self._dataPtr

   ;; Process any characters authorized by texToIDL
   text = texToIDL(text)

   ;; Apply the coordinate system, if you have one.
   IF Obj_Class(self._coords) EQ 'MAPCOORD' THEN BEGIN
      IF Obj_Valid(self._coords) THEN BEGIN
      
        ;; If you have MAPCOORD object, then determine if the points to plot
        ;; are in lat/lon or UV coordinate space. The MapCoord object sets up
        ;; a UV coordinate space. The values to be plotted here are in lat/lon
        ;; space (normally), so they have to be converted to XY space to be plotted.
        IF ~self.uvcoords THEN BEGIN
            mapStruct = self._coords -> GetMapStructure()
            uv = Map_Proj_Forward(self.x, self.y, MAP_STRUCTURE=mapStruct)
            _x = uv[0]
            _y = uv[1]
        ENDIF ELSE BEGIN
            _x = self.x
            _y = self.y
        ENDELSE
      ENDIF  ELSE BEGIN
        _x = self.x
        _y = self.y
      ENDELSE
   ENDIF ELSE BEGIN
        _x = self.x
        _y = self.y
        self -> ApplyCoords
   ENDELSE

   ;; Always do font substitution for PostScript or Printer output.
   theFont = self.font
   IF (!D.Name EQ 'PS' OR !D.Name EQ 'PRINTER') AND (theFont EQ 0) THEN theFont = 1

   ;; Calculate the thickness factor
   IF !D.Name EQ 'PS' THEN factor = 2.5 ELSE factor = 1.0

   ;; Output the string without actually drawing it.
   IF (!D.Name EQ 'PS' OR !D.Name EQ 'PRINTER') THEN BEGIN
       theWidth = self.width
   ENDIF ELSE BEGIN
      IF theFont EQ 1 THEN widthFont=0 ELSE widthFont=theFont
      XYOuts, _x, _y, text, Alignment=self.alignment/2.0, Orientation=self.orientation, $
         Font=widthFont-1, Charsize=-self.charsize, Color=cgColor(self.color), Width=theWidth, $
         CharThick=self.thickness*factor
   ENDELSE

   ;; Assign the width. Start by making it slightly larger than it is.
   theWidth = theWidth + 0.04
   self._coords -> GetProperty, XRANGE=xr
   self.width = theWidth * (xr[1] - xr[0])

   ;; Calculate the text box.
   self -> CalculateBoundaryBox

   ;; Draw the background if required.
   IF self.background THEN PolyFill, self.box[0,*], self.box[1,*], $
                                     Fill=1, $
                                     Color=cgColor(self.bg_color), $
                                     NoClip=1-keyword_set(self.clip), $
                                     /Normal

   ; Apply the coordinate system, if you have one.
   self -> ApplyCoords

   

   ;; Output the string.
   IF (!D.Name EQ 'PS' OR !D.Name EQ 'PRINTER') THEN BEGIN
      XYOuts, self.x, self.y, text, $
              Alignment=self.alignment/2.0, $
              Orientation=self.orientation, $
              Font=theFont-1, $
              Charsize=self.charsize, $
              Color=cgColor(self.color), $
              CharThick=self.thickness*factor, $
              NoClip=1-keyword_set(self.clip)
   ENDIF ELSE BEGIN
      XYOuts, self.x, self.y, text, $
              Alignment=self.alignment/2.0, $
              Orientation=self.orientation, $
              Font=theFont-1, $
              Charsize=self.charsize, $
              Color=cgColor(self.color), $
              CharThick=self.thickness*factor, $
              NoClip=1-keyword_set(self.clip)
   ENDELSE

   ;; Restore decomposed mode
   SetDecomposedState, currentState

   ;; Restore color table
   TVLCT, r, g, b

   ;; Draw the children
   self -> CATATOM::Draw, _EXTRA=extra

   ;; Report completion
   self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;        IGL_Text::EVENT_HANDLER
;
; PURPOSE:
;
;        This method is the event handler for the IGL_Text object. It will typically
;        be used to respond to events from widget objects created in the CONTROLPANEL
;        method.
;
; SYNTAX:
;
;        This method is called automatically by the event handling mechanism.
;
; ARGUMENTS:
;
;       event: The event structure as described in the IDL help files, except
;              that the ID, TOP and HANDLER tags will be object references.
;
; KEYWORDS:
;
;       None.
;
;-
;*****************************************************************************************************
PRO IGL_Text::EventHandler, event

   @cat_pro_error_handler

   ; Get the name of the widget generating the event. Branch on this.
   event.ID -> GetProperty, Name=eventName
   
   CASE eventName OF

      'IGL_TEXT PROPERTYSHEET': BEGIN

         IF event.type EQ 0 THEN BEGIN ; Button DOWN event.

            CASE StrUpCase(event.identifier) OF

               'COLOR': BEGIN

                  event.component -> GetProperty, Color=color
                  event.id -> GetProperty, ID=group_leader
                  color = PickColorName(color, Group_Leader=group_leader)
                  event.component -> SetProperty, Color=color

                  ;; Refresh the graphics hierarchy
                  IGL_RefreshDraw, event.component, /Buffered_Output

               END

               'BG_COLOR': BEGIN

                  event.component -> GetProperty, BG_Color=color
                  event.id -> GetProperty, ID=group_leader
                  color = PickColorName(color, Group_Leader=group_leader)
                  event.component -> SetProperty, BG_Color=color

                  ;; Refresh the graphics hierarchy
                  IGL_RefreshDraw, event.component, /Buffered_Output

                  END

               ELSE: BEGIN

                  component = event.component
                  identifier = event.identifier
                  event.id -> GetProperty, Value=value, Component=component, Property_Value=identifier
                  event.component -> SetPropertyByIdentifier, identifier, value

                  ;; Refresh the graphics hierarchy
                  IF OBJ_VALID(event.component) THEN IGL_RefreshDraw, event.component, /Buffered_Output

               END

            ENDCASE

         ENDIF                  ; of BUTTON DOWN EVENT
         
      END                  

      ;; If you can't handle the event here. Pass it along to superclass EventHandler
      ELSE: self -> TextLine::EventHandler, event

   ENDCASE

   ;; Report completion
   IF Obj_Valid(self) THEN self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::GETHEIGHT
;
; PURPOSE:
;
;       This method is used to calculate the height of the string on
;       the current display. The method assumes a valid display
;       (e.g. a valid window if current device is the window device) at the
;       time it is called. By default, the height is returned in
;       normalized coordinates.
;
; SYNTAX:
;
;       heihg = self -> GetHeight()
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     DATA:    Set this keyword to have the result returned in data coordinates.
;
;     DEVICE:  Set this keyword to have the result returned in device coordinates.
;
;     NORMAL:  Set this keyword to have the result returned in
;              normalized coordinates (this is the default).
;
;-
;*****************************************************************************************************
FUNCTION IGL_Text::GetHeight, DATA=data, NORMAL=normal, DEVICE=device

  @cat_func_error_handler  

  ;; Get the character size and font
  self -> GetProperty, charsize=charsize, font=font

  ;; Character size does not apply for Hardware fonts except if the
  ;; device has scalable pixels
  IF (font EQ 1) AND ((!D.Flags AND 1) EQ 0) THEN charsize = 1

  ;; Calculate the height in the desired units

  IF (keyword_set(DATA)) THEN BEGIN
     
     IF OBJ_VALID(self._coords) THEN BEGIN
        self._coords -> GetProperty, XRANGE=xr, YRANGE=yr, POSITION=pos
        height = Abs(yr[1] - yr[0]) / (!D.Y_Size * Abs(pos[3]-pos[1])) * !D.Y_CH_SIZE * charsize ; DATA units
     ENDIF ELSE height = 0
     
  ENDIF
  
  IF keyword_set(DEVICE) THEN height = charsize*!D.Y_CH_SIZE ; DEVICE units
  
  IF keyword_set(NORMAL) THEN height = charsize*!D.Y_CH_SIZE/float(!D.Y_SIZE) ; NORMALIZED units

  ;; If no keyword is set, use normalized coordinates
  IF N_Elements(height) EQ 0 THEN height = charsize*!D.Y_CH_SIZE/float(!D.Y_SIZE)

  ;; Report completion
  self -> Report, /Completed

  RETURN, height

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::GETWIDTH
;
; PURPOSE:
;
;       This method is used to calculate the widtht of the string on
;       the current display. The method assumes a valid display
;       (e.g. a valid window if current device is the window device) at the
;       time it is called. By default, the width is returned in
;       normalized coordinates.
;
; SYNTAX:
;
;       heihg = self -> GetWidth()
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     DATA:    Set this keyword to have the result returned in data coordinates.
;
;     DEVICE:  Set this keyword to have the result returned in device coordinates.
;
;     NORMAL:  Set this keyword to have the result returned in
;              normalized coordinates (this is the default).
;
;-
;*****************************************************************************************************
FUNCTION IGL_Text::GetWidth, DATA=data, NORMAL=normal, DEVICE=device

  ;; Get the text properties that affect the output
  self -> GetProperty, text=text, $
                       alignment=align, $
                       orientation=orient, $
                       font=font, $
                       thickness=charthick, $
                       charsize=charsize


  ;; Draw an invisible text on the display to get the width
  XYOuts, 0.99, 0.99, texToIDL(text), $
          Alignment=align, $
          Orientation=orient, $
          Font=font-1, $
          Charsize=-1, $
          Width=textWidth, $
          CharThick=charthick, $
          /Normal

  ;; Character size does not apply for Hardware fonts except if the
  ;; device has scalable pixels
  IF (font EQ 1) AND ((!D.Flags AND 1) EQ 0) THEN charsize = 1

  ;; Calculate the width in the desired units

  IF keyword_set(DATA) THEN BEGIN
     
     IF OBJ_VALID(self._coords) THEN BEGIN
        self._coords -> GetProperty, XRANGE=xr, YRANGE=yr, POSITION=pos
        width = Abs(xr[1] - xr[0]) / (!D.X_Size * Abs(pos[2]-pos[0])) * textWidth * charsize ; DATA units
     ENDIF ELSE width = 0
     
  ENDIF

  IF keyword_set(DEVICE) THEN width = textWidth*!D.X_SIZE*charsize ; DEVICE units
  
  IF keyword_set(NORMAL) THEN width = textWidth*charsize ; NORMALIZED units

  ;; If no keyword is set, use normalized coordinates
  IF N_ELEMENTS(width) EQ 0 THEN width = textWidth*charsize

  ;; Report completion
  self -> Report, /Completed
  
  RETURN, width

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::GETPROPERTY
;
; PURPOSE:
;
;       This method is used to get the object's properties.
;
; SYNTAX:
;
;       self -> GetProperty
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     CLIP:        Flag indicating if the text is clipped.
;
;     _REF_EXTRA:  Any keywords appropriate for the superclass GetProperty method.
;-
;*****************************************************************************************************
PRO IGL_Text::GetProperty, CLIP=clip, $
                           _REF_EXTRA=extraKeywords

   @cat_pro_error_handler

   IF ARG_PRESENT(clip) THEN clip = self.clip

   IF (N_ELEMENTS(extraKeywords) GT 0) THEN self -> TEXTLINE::GetProperty, _EXTRA=extraKeywords

   self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::SETPROPERTY
;
; PURPOSE:
;
;       This method is used to set the object's properties.
;
; SYNTAX:
;
;       self -> SetProperty
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     CLIP:        Flag indicating if the text is clipped.
;
;     DELETE:      Set this keyword to destroy the object.
;
;     _EXTRA:      Any keyword accepted by the SetProperty method of
;                  the TEXTLINE superclass.
;-
;*****************************************************************************************************
PRO IGL_Text::SetProperty, CLIP=clip, $
                           DELETE=delete, $
                           _EXTRA=extraKeywords

   @cat_pro_error_handler

   IF (N_elements(clip) NE 0) THEN self.clip = keyword_set(clip)

   IF (N_ELEMENTS(extraKeywords) GT 0) THEN self -> TEXTLINE::SetProperty, _EXTRA=extraKeywords

   IF (keyword_set(delete)) THEN BEGIN
      self -> SetProperty, Visible=0
      IGL_RefreshDraw, self, /Buffered_Output
      Obj_Destroy, self._controlPanel
      self -> GetProperty, Parent=parents
      self -> SendMessage, 'OBJECT_DELETED', DATA=self
      FOR j=0,N_Elements(parents)-1 DO IF OBJ_VALID(parents) THEN parents[j] -> Remove, self
      Obj_Destroy, self
      RETURN
   ENDIF

   IF (obj_valid(self)) THEN self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::CLEANUP
;
; PURPOSE:
;
;       This is the IGL_Text object class destructor method.
;
; SYNTAX:
;
;       Called automatically when the object is destroyed.
;
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     None.
;-
;*****************************************************************************************************
PRO IGL_Text::CLEANUP

   @cat_pro_error_handler

   ;; Cleanup the superclass
   self -> TEXTLINE::CLEANUP

   ;; Report completion
   self -> Report, /Completed
   
END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Text::INIT
;
; PURPOSE:
;
;       This is the IGL_Text object class initialization method
;
; SYNTAX:
;
;       Called automatically when the object is created.
;
; ARGUMENTS:
;
;     text:        The single line of text to display.
;
; KEYWORDS:
;
;     CLIP:        Flag indicating if the text is clipped.
;
;     _EXTRA:      Any keywords appropriate for the TEXTLINE::Init method.
;-
;*****************************************************************************************************
FUNCTION IGL_Text::INIT, text, $
                         COORD_OBJECT=coord_object, $
                         COLOR=color, $
                         FONT=font, $
                         CLIP=clip, $
                         _Extra=extraKeywords


   @cat_func_error_handler

   IF (n_elements(color) EQ 0) THEN color = !IGL_P.color
   IF (n_elements(font) EQ 0) THEN font = !IGL_P.font+1

   ;; Call superclass INIT method
   ok = self -> TEXTLINE::Init(text,COLOR=color,FONT=(0 > font < 2),_Extra=extraKeywords)

   IF (NOT ok) THEN RETURN, 0

   ;; If there is no available coordinate system, create a normalized one
   IF OBJ_ISA_VALID(coord_object,'CatCoord') EQ 0 THEN coord_object = obj_new('IGL_Coord',XRANGE=[0,1],YRANGE=[0,1],POSITION=[0,0,1,1])
   self -> SetProperty, COORD_OBJECT=coord_object
   
   ;; Load object
   self.clip = keyword_set(clip)

   ;; Change default name and description for the TEXTLINE object
   self -> SetProperty, name='Text', description='Text annotation'

   ;; Set up editable properties
   self -> SetPropertyAttribute, 'NAME', sensitive=0
   self -> SetPropertyAttribute, 'DESCRIPTION', sensitive=0
   self -> SetPropertyAttribute, 'X', /HIDE
   self -> SetPropertyAttribute, 'Y', /HIDE
   self -> RegisterProperty, 'CLIP', /boolean, name='Clipping'

   ;; Report completion
   self -> Report, /Completed

   RETURN, 1

END


;*****************************************************************************************************
;
; NAME:
;       IGL_TEXT CLASS DEFINITION
;
; PURPOSE:
;
;       This is the IGL_Text object's structure definition code.
;
;*****************************************************************************************************
PRO IGL_Text__DEFINE, class

  COMPILE_OPT idl2, HIDDEN

  class = {IGL_Text, $      
           INHERITS TEXTLINE, $   ; This is a TEXTLINE object
           clip:0B $              ; Flag indicating if the text is clipped
          }

  struct_hide, class

END
